#ifndef _VIRTUAL_TEXTURE_MANAGER_H
#define _VIRTUAL_TEXTURE_MANAGER_H

#include "Engine.h"
using namespace Engine;

/*
	   512, 1024, (2048)
	   128,  256,  (512)
	    32,   64,  (128)
	     8,   16,   (32)
	     2,    4,    (8)
	   4x4,  2x2,	1x1
*/


	//
	// Virtual Texture Manager
	//

	class _EXPORT_ CVirtualTextureManager : public CEngBaseObject
	{
	public:
		// types
#		pragma pack(push,1)
		struct TIndex
		{
			uint16		x,y;
			uint8		size;
			uint8		layer;
		};
#		pragma pack(pop)

		typedef TIndex		index_t;


		struct TDescriptor
		{
			struct TTexture
			{
				e_rt_format::type	eFormat;
				e_tex_swizzle::type	eSwizzle;
				e_tex_flags::type	eFlags;
				///
				TTexture():
					eFormat(e_rt_format::UNKNOWN), eSwizzle(e_tex_swizzle::AUTO),
					eFlags(e_tex_flags::UNKNOWN)
				{}
				TTexture(e_rt_format::type format, e_tex_swizzle::type swizzle, e_tex_flags::type flags):
					eFormat(format), eSwizzle(swizzle), eFlags(flags)
				{}
			};

			array<TTexture>		aTexInfo;
			uvec2				vDimension;		// dimensin of texture
			uint				uBlockDim;		// max dimension of texture block
			///
			TDescriptor() {}
		};

		struct TTexture
		{
			CTexture2DPtr		pMain,			// texture with blocks
								pReserve;		// texture for swap, async add, ... (size = max block)
		};


	protected:
		struct TBlock
		{
			index_t				sUpLevel;
			uint16				uBits;			// 4x4 bitfield (0-empty, 1-have texture)
			///
			TBlock(): uBits(0) { memset( &sUpLevel, 0, sizeof(sUpLevel) ); }
		};

		struct TLayer
		{
			typedef static_array< usize, 17, TFastCopyStrategy<usize> >		usize_starray_t;

			array< TBlock >		aBlocks;
			uint8				uMinBlockPO2;	// min block pow of 2
			///
			TLayer(): uMinBlockPO2(0) {}
		};

	protected:
		// values
		array< TTexture >		_aTextures;
		array< TLayer >			_aLayers;		// from min to max size
		uvec2					_vDimension;	// texture dimension in pow of 2
		uint					_uMaxBlockDim;	// max dimension of texture block
		TCriticalSection		_sCSLockTex;
		index_t					_sDefaultIndex;	// index of 1x1 default texture

	protected:
		void _SwapTextures(const index_t &i0, const index_t &i1);
		bool _GetFreeTexture(const uvec2 &vDim, index_t &sIndex);
		bool _AllocTexture(uint uLayer, uint &uBlockIndex);

		inline uint _MaxBlockSize() const
		{
			return 1 << (_aLayers.Back().uMinBlockPO2 + 2);
		}

		static uint _s_GetLayer(uint uDim)
		{
			return (GetPOT( uDim )-1) >> 1;
		}

		static uvec2 _s_GetSize(const index_t &i)
		{
			return uvec2( i.size & 0x3, (i.size >> 2) & 0x3 ) << ((i.layer<<1)+1);
		}

		static bool _s_CmpIndex(const index_t &i0, const index_t &i1)
		{
			return (i0.x == i1.x) && (i0.y == i1.y);
		}

		virtual void Release()	{ delete this; }

	public:
		CVirtualTextureManager();
		~CVirtualTextureManager();

		bool Create(const TDescriptor &sDescr);
		void Destroy();

		void Bind(uint8 uFirst = 0) const;
		void Unbind(uint8 uFirst = 0) const;

		void BindTexture(uint uTex, uint8 uStage = 0) const;
		void UnbindTexture(uint uTex, uint8 uStage = 0) const;

		void AttachAll(FBO_t *pFBO, e_fbo_attach::type eFirst);
		void AttachAllReserve(FBO_t *pFBO, e_fbo_attach::type eFirst);
		void AttachTex(FBO_t *pFBO, e_fbo_attach::type eAttach, uint uTexture);
		void AttachReserve(FBO_t *pFBO, e_fbo_attach::type eAttach, uint uTexture);

		bool AllocTexture(const uvec2 &vDim, urect &sTexCoord, index_t &sIndex);
		bool AddTexture(const TImage2D &sImage, const index_t &sIndex, uint uTexture);
		bool AddTextureAsync(const TImage2D &sImage, const index_t &sIndex, uint uTexture);

		bool AddTexture(const array<TImage2D> &aImages, urect &sTexCoord, index_t &sIndex);
		bool AddTextureAsync(const array<TImage2D> &aImages, urect &sTexCoord, index_t &sIndex);
		bool FreeTexture(const index_t& sIndex);

		bool Test();

		const urect		GetCoords(const index_t &sIndex) const;

		uint			NumTextures()	const			{ return _aTextures.Count(); }
		const uvec2 &	GetDimension()	const			{ return _vDimension; }
		
		const array<TTexture> & GetTextures()	const	{ return _aTextures; }
	};
	
	
	
	inline void CVirtualTextureManager::Bind(uint8 first) const
	{
		for (usize i = 0; i < _aTextures.Count(); ++i)
			_aTextures[i].pMain->Bind( first + i );
	}


	inline void CVirtualTextureManager::Unbind(uint8 first) const
	{
		for (usize i = 0; i < _aTextures.Count(); ++i)
			_aTextures[i].pMain->Unbind( first + i );
	}
	

	inline void CVirtualTextureManager::BindTexture(uint uTex, uint8 uStage) const
	{
		_aTextures[uTex].pMain->Bind( uStage );
	}


	inline void CVirtualTextureManager::UnbindTexture(uint uTex, uint8 uStage) const
	{
		_aTextures[uTex].pMain->Unbind( uStage );
	}


	inline void CVirtualTextureManager::AttachAll(FBO_t *pFBO, e_fbo_attach::type eFirst)
	{
		for (usize i = 0; i < _aTextures.Count(); ++i)
		{
			pFBO->AttachTexture2D( _aTextures[i].pMain->GetIdent(), gl_texture::TEXTURE_2D, eFirst + i, 0 );
		}
	}


	inline void CVirtualTextureManager::AttachAllReserve(FBO_t *pFBO, e_fbo_attach::type eFirst)
	{
		for (usize i = 0; i < _aTextures.Count(); ++i)
		{
			pFBO->AttachTexture2D( _aTextures[i].pReserve->GetIdent(), gl_texture::TEXTURE_2D, eFirst + i, 0 );
		}
	}


	inline void CVirtualTextureManager::AttachTex(FBO_t *pFBO, e_fbo_attach::type eAttach, uint uTexture)
	{
		pFBO->AttachTexture2D( _aTextures[uTexture].pMain->GetIdent(), gl_texture::TEXTURE_2D, eAttach, 0 );
	}


	inline void CVirtualTextureManager::AttachReserve(FBO_t *pFBO, e_fbo_attach::type eAttach, uint uTexture)
	{
		pFBO->AttachTexture2D( _aTextures[uTexture].pReserve->GetIdent(), gl_texture::TEXTURE_2D, eAttach, 0 );
	}
	

	inline const urect CVirtualTextureManager::GetCoords(const index_t &sIndex) const
	{
		return urect( uvec2( sIndex.x, sIndex.y ), uvec2( sIndex.x, sIndex.y ) + _s_GetSize( sIndex ) );
	}


#endif	// _VIRTUAL_TEXTURE_MANAGER_H